package it.tukano.reflection;

import it.tukano.fun.Equals;
import it.tukano.fun.Function1;
import it.tukano.fun.Function2;
import it.tukano.log.Log;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A string-method association that can be used as an ActionListener. The
 * action listener requires methods with an ActionEvent as argument and uses
 * the action command of the event to call the reflective method
 * <pre>
 * CallMap map = new CallMap()
 *  .add("some key 1", this, "method1", ActionEvent.class)
 *  .add("some key 2", this, "method2", ActionEvent.class);
 * JButton b1 = new JButton("some key 1");//label of the button becomes the action command
 * b1.addActionListener(map);//when pressed invokes method1
 * JButton b2 = new JButton("some label");
 * b2.setActionCommand("some key 2");//action command is the key for method2 in call map
 * b2.addActionListener(map);//when pressed invokes method2
 * </pre>
 * @author pgi
 */
public class CallMap implements ActionListener {

    private final List<Function1<Object, Boolean>> keys = new ArrayList<Function1<Object, Boolean>>();
    private final List<MethodExt> values = new ArrayList<MethodExt>();

    /**
     * Add a method to the call map
     * @param key the key of the method in the map. If the map is used as an
     * action listener then the key will be checked against the ActionCommand of
     * the ActionEvent to call the appropriate method
     * @param instance the instance used to invoke the method
     * @param methodName the name of the method to invoke
     * @param params the parameters of the method. If used as an ActionListener,
     * the method must have one ActionEvent as parameter
     * @return this
     */
    public CallMap add(final String key, Object instance, String methodName, Class<?>... params) {
        Function1<Object, Boolean> stringEquals = new Function1<Object, Boolean>() {

            public Boolean apply(Object p) {
                return p != null && p.equals(key);
            }
        };
        keys.add(stringEquals);
        values.add(new MethodExt(instance, methodName, params));
        return this;
    }
    
    /**
     * Add a method to the call map
     * @param key the key of the method in the map. If the map is used as an
     * action listener then the key will be checked against the ActionCommand of
     * the ActionEvent to call the appropriate method
     * @param instance the instance used to invoke the method
     * @param methodName the name of the method to invoke
     * @param params the parameters of the method. If used as an ActionListener,
     * the method must have one ActionEvent as parameter
     * @return this
     */
    public CallMap add(Object key, Object instance, String methodName, Class<?>... params) {
        Equals equals = new Equals(key);
        keys.add(equals);
        values.add(new MethodExt(instance, methodName, params));
        return this;
    }
    
    /**
     * Add a method to the call map
     * @param key the function that when true causes the invocation of the method
     * @param instance the owner of the called method
     * @param methodName the name of the method to call
     * @param params the parameters of the method
     * @return this
     */
    public CallMap add(Function1<Object, Boolean> key, Object instance, String methodName, Class<?>... params) {
        keys.add(key);
        values.add(new MethodExt(instance, methodName, params));
        return this;
    }

    /**
     * Calls the method associated with the given key, using args as the parameter's
     * list
     * @param key the key of the method to call
     * @param args the list of parameters to use
     */
    public void call(Object key, Object... args) {
        for(int i = 0; i < keys.size(); i++) {
            if(keys.get(i).apply(key)) values.get(i).invoke(args);
        }
    }

    /**
     * As an ActionListener, the call map responds to ActionEvents. This uses
     * the ActionCommand of the ActionEvent as the key to find a suitable method
     * to call. If the method exists, it is invoked passing the ActionEvent as
     * argument.
     * @param e the event to parse
     */
    public void actionPerformed(ActionEvent e) {
        call(e.getActionCommand(), e);
    }
}
